package me.lwn.auth.security.oauth2.server.authorization.web.authentication;

import me.lwn.auth.security.oauth2.server.authorization.authentication.OAuth2SmsAuthenticationToken;
import me.lwn.auth.security.utils.LocalMessageSource;
import me.lwn.auth.security.utils.OAuth2EndpointUtils;
import me.lwn.auth.security.web.authentication.LoginLockedSupport;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static me.lwn.auth.security.utils.Constants.*;

public class OAuth2SmsAuthenticationConverter implements AuthenticationConverter {

    protected MessageSourceAccessor messages = LocalMessageSource.getAccessor();
    private LoginLockedSupport loginLockedSupport;

    public void setLoginLocked(LoginLockedSupport loginLockedSupport) {
        this.loginLockedSupport = loginLockedSupport;
    }

    @Override
    public Authentication convert(HttpServletRequest request) {

        // grant_type (REQUIRED)
        String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
        if (!SPRING_SECURITY_OAUTH_GRANT_TYPE_SMS_VALUE.equals(grantType)) {
            return null;
        }

        // check if account is locked
        loginLockedSupport.check(request);

        MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);

        // scope (OPTIONAL)
        String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
        if (StringUtils.hasText(scope) &&
                parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST,
                    messages.getMessage("OAuth2SmsAuthenticationConverter.scopeRequired"), "");
        }

        Set<String> requestedScopes = null;
        if (StringUtils.hasText(scope)) {
            requestedScopes = new HashSet<>(
                    Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
        }

        // mobile (REQUIRED)
        String mobile = parameters.getFirst(SPRING_SECURITY_OAUTH_FORM_MOBILE_KEY);
        if (!StringUtils.hasText(mobile) || parameters.get(SPRING_SECURITY_OAUTH_FORM_MOBILE_KEY).size() != 1) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST,
                    messages.getMessage("OAuth2SmsAuthenticationConverter.mobileRequired"), "");
        }

        // code (REQUIRED)
        String smsCode = parameters.getFirst(SPRING_SECURITY_OAUTH_FORM_MOBILE_VALUE);
        if (!StringUtils.hasText(smsCode) || parameters.get(SPRING_SECURITY_OAUTH_FORM_MOBILE_VALUE).size() != 1) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST,
                    messages.getMessage("OAuth2SmsAuthenticationConverter.codeRequired"), "");
        }

        Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
        if (clientPrincipal == null) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST,
                    messages.getMessage("OAuth2SmsAuthenticationConverter.invalidClient"), "");
        }

        Map<String, Object> additionalParameters = parameters
                .entrySet()
                .stream()
                .filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) &&
                        !e.getKey().equals(OAuth2ParameterNames.SCOPE))
                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));

        return new OAuth2SmsAuthenticationToken(clientPrincipal, requestedScopes, additionalParameters);
    }
}
